#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/timer.h>
#include <linux/sched.h>
#include <linux/leds.h>
#include <linux/reboot.h>
#include <linux/hrtimer.h>

struct gpio_pwm_trig_data {
	unsigned int delay_on;
	unsigned int delay_off;
	struct hrtimer timer;
	struct led_classdev *led_cdev;
};

static inline int led_get_brightness(struct led_classdev *led_cdev)
{
	return led_cdev->brightness;
}
static enum hrtimer_restart gpio_pwm_function(struct hrtimer *data)
{
	int brightness  = 0;
	u64 ns = 0;

	struct gpio_pwm_trig_data *pwm_data = container_of(data, struct gpio_pwm_trig_data, timer);
	struct led_classdev *led_cdev = pwm_data->led_cdev;

	if(pwm_data->delay_on == 0 || pwm_data->delay_off == 0)
		return HRTIMER_NORESTART;

	if((led_cdev->brightness == LED_OFF))
	{
		brightness = led_cdev->max_brightness;
		ns = pwm_data->delay_on * 1000;
	}
	else
	{
		brightness = LED_OFF;
		ns = pwm_data->delay_off * 1000;
	}


	if (!(led_cdev->flags & LED_SUSPENDED))
		led_cdev->brightness_set(led_cdev, brightness);

	led_cdev->brightness = brightness;

	hrtimer_forward_now(&pwm_data->timer, ns_to_ktime(ns));

	return HRTIMER_RESTART;
}

static ssize_t gpio_pwm_delay_on_show(struct device *dev,
	struct device_attribute *attr, char *buf)
{
	struct led_classdev *led_cdev = dev_get_drvdata(dev);

	struct gpio_pwm_trig_data *pwm_data = led_cdev->trigger_data;

	return sprintf(buf, "%d\n", pwm_data->delay_on);
}
static ssize_t gpio_pwm_delay_on_store(struct device *dev,
	struct device_attribute *attr, const char *buf, size_t size)
{
	struct led_classdev *led_cdev = dev_get_drvdata(dev);
	struct gpio_pwm_trig_data *pwm_data = led_cdev->trigger_data;
	unsigned int val;
	u64 nsec =0;
	val = simple_strtol(buf, NULL, 10);


	pwm_data->delay_on = val;
	if(pwm_data->delay_on != 0)
	{
		hrtimer_cancel(&pwm_data->timer);
		nsec = pwm_data->delay_on*1000;

		if(pwm_data->delay_on != 0 && pwm_data->delay_off != 0)
			hrtimer_start(&pwm_data->timer, ktime_add_ns(ktime_get(), nsec),
					HRTIMER_MODE_ABS);
	}

	return size;
}
static DEVICE_ATTR(delay_on, S_IWUSR | S_IRUGO, gpio_pwm_delay_on_show, gpio_pwm_delay_on_store);


static ssize_t gpio_pwm_delay_off_show(struct device *dev,
	struct device_attribute *attr, char *buf)
{
	struct led_classdev *led_cdev = dev_get_drvdata(dev);

	struct gpio_pwm_trig_data *pwm_data = led_cdev->trigger_data;

	return sprintf(buf, "%d\n", pwm_data->delay_off);
}
static ssize_t gpio_pwm_delay_off_store(struct device *dev,
        struct device_attribute *attr, const char *buf, size_t size)
{
	struct led_classdev *led_cdev = dev_get_drvdata(dev);
	struct gpio_pwm_trig_data *pwm_data = led_cdev->trigger_data;
	unsigned int val;
	u64 nsec =0;
	val = simple_strtol(buf, NULL, 10);


	pwm_data->delay_off = val;
	if(pwm_data->delay_off != 0)
	{
		hrtimer_cancel(&pwm_data->timer);
		nsec = pwm_data->delay_off*1000;
		
		if(pwm_data->delay_on != 0 && pwm_data->delay_off != 0)
			hrtimer_start(&pwm_data->timer, ktime_add_ns(ktime_get(), nsec),
					HRTIMER_MODE_ABS);
	}

	return size;
}
static DEVICE_ATTR(delay_off, S_IWUSR | S_IRUGO, gpio_pwm_delay_off_show, gpio_pwm_delay_off_store);



static void gpio_pwm_trig_activate(struct led_classdev *led_cdev)
{
	struct gpio_pwm_trig_data *pwm_data;
	int ret;

	pwm_data = kzalloc(sizeof(struct gpio_pwm_trig_data), GFP_KERNEL);
	if (!pwm_data)
	{
		printk("%s:%d\n", __FUNCTION__, __LINE__);
		return;
	}
	ret = device_create_file(led_cdev->dev, &dev_attr_delay_on);
	if (ret)
	{
		printk("%s:%d\n", __FUNCTION__, __LINE__);
		goto free_mem;
	}

	ret = device_create_file(led_cdev->dev, &dev_attr_delay_off);
	if (ret)
	{
		printk("%s:%d\n", __FUNCTION__, __LINE__);
		goto remove_file;
	}


	led_cdev->trigger_data = pwm_data;
	pwm_data->led_cdev = led_cdev;

	hrtimer_init(&pwm_data->timer, CLOCK_REALTIME, HRTIMER_MODE_ABS);
	pwm_data->timer.function = gpio_pwm_function;
	pwm_data->delay_on = 0;
	pwm_data->delay_off = 0;
	led_cdev->activated = true;
	
	return;


remove_file:
	device_remove_file(led_cdev->dev, &dev_attr_delay_on);
free_mem:
	kfree(pwm_data);
}

static void gpio_pwm_trig_deactivate(struct led_classdev *led_cdev)
{
	struct gpio_pwm_trig_data *pwm_data = led_cdev->trigger_data;

	if (led_cdev->activated) {
		device_remove_file(led_cdev->dev, &dev_attr_delay_off);
		device_remove_file(led_cdev->dev, &dev_attr_delay_on);
		hrtimer_cancel(&pwm_data->timer);
		kfree(pwm_data);
		led_cdev->activated = false;
	}
}

static struct led_trigger gpio_pwm_led_trigger = {
	.name     = "gpiopwm",
	.activate = gpio_pwm_trig_activate,
	.deactivate = gpio_pwm_trig_deactivate,
};




static int __init gpio_pwm_trig_init(void)
{
	int rc = led_trigger_register(&gpio_pwm_led_trigger);

	return rc;
}

static void __exit gpio_pwm_trig_exit(void)
{
	led_trigger_unregister(&gpio_pwm_led_trigger);
}

module_init(gpio_pwm_trig_init);
module_exit(    gpio_pwm_trig_exit);

MODULE_AUTHOR("jerry_chg");
MODULE_DESCRIPTION("Gpio Pwm  Trigger");
MODULE_LICENSE("GPL");

